// 解析 CSS
import css from "css";
import { Node, style } from "./dom/dom";

const rules: Array<css.Rule> = [];

// 收集添加css规则
export function addCSSRules(text: string) {
  const ast = css.parse(text);
  rules.push(...ast.stylesheet.rules);
}

export function computeCSS(element: Node) {
  // 每次有 style 属性进来时，应该要触发一次重新计算
  // style 通常是在 html 和 head 之后，它们一般没有 CSS

  // eslint里不允许对函数参数进行修改，除了使用disable我没有想到其他方案
  // eslint-disable-next-line no-param-reassign
  if (!element.computedStyle) element.computedStyle = [];

  for (const rule of rules) {
    const selectorParts = rule.selectors[0].split(" ").reverse();

    // eslint中不允许continue，可以更换循环结构解决，时间关系先disable
    // eslint-disable-next-line no-continue
    if (!match(element, selectorParts[0])) continue;
    else {
      let i = 1;
      let curr = element.parent;
      let matched = false;
      while (curr) {
        if (match(curr, selectorParts[i])) i += 1;
        curr = curr.parent;
      }
      if (i >= selectorParts.length) matched = true;

      if (matched) {
        const sp = getSpecificity(rule.selectors[0]);
        const { computedStyle } = element;
        for (const declaration of rule.declarations) {
          const key = (declaration as css.Declaration).property;

          const { value } = declaration as css.Declaration;
          if (!computedStyle.some((k) => k.key === key)) {
            computedStyle.unshift({ key, value, specificity: sp });
          } else if (
            compare(computedStyle[getComputedStyleIndex(computedStyle, key)].specificity, sp) < 0
          ) {
            computedStyle[getComputedStyleIndex(computedStyle, key)] = {
              key,
              value,
              specificity: sp,
            };
          }
        }
      }
    }
  }
}

function match(element: Node, compoundSelector: string) {
  if (!compoundSelector || !element.attributes) return false;

  // ?= 是一个零宽断言，会找匹配字符的前面零宽的位置
  // 以 . 或则 # 将复合选择器切开
  const selectors = compoundSelector.split(/(?=[.#])/);

  let matched = false;
  for (const selector of selectors) {
    // 处理简单选择器，这里只处理 class、id、tag 选择器
    if (selector.charAt(0) === "#") {
      const attr = element.attributes.filter((att) => att.name === "id")[0];
      if (!attr) return false;

      matched = attr.value === selector.replace("#", "");
    } else if (selector.charAt(0) === ".") {
      const attr = element.attributes.filter((att) => att.name === "class")[0];
      if (!attr) return false;

      // 处理 class 用 空格 分开的情况
      const values = attr.value.trim().split(" ");
      for (const val of values) {
        matched = val === selector.replace(".", "");
        if (matched) break;
      }
    } else {
      matched = element.tagName === selector;
    }
  }
  return matched;
}

function getSpecificity(selector: string) {
  const p: number[] = [0, 0, 0, 0];
  const selectorParts = selector.split(" ");
  for (const part of selectorParts) {
    // 处理复合选择器
    const selectors = part.split(/(?=[.#])/);
    for (const se of selectors) {
      if (se.charAt(0) === "#") {
        p[1] += 1;
      } else if (se.charAt(0) === ".") {
        p[2] += 1;
      } else {
        p[3] += 1;
      }
    }
  }
  return p;
}

// 比较优先级
function compare(sp1: Array<number>, sp2: Array<number>) {
  if (sp1[0] - sp2[0]) return sp1[0] - sp2[0];
  if (sp1[1] - sp2[1]) return sp1[1] - sp2[1];
  if (sp1[2] - sp2[2]) return sp1[2] - sp2[2];
  return sp1[3] - sp2[3];
}

function getComputedStyleIndex(computedStyle: Array<style>, key: string) {
  let i = 0;
  while (i < computedStyle.length) {
    if (computedStyle[i].key === key) return i;
    i += 1;
  }
  return -1;
}

module.exports = {
  addCSSRules,
  computeCSS,
};
